package client.io;

import client.Client;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import shared.client.io.ClientCommand;
import shared.util.Pair;

/**
 *
 * This utility class handles IO from the user.
 * It provides useful methods for getting and printing specifically
 * formatted input/output.
 *
 * @author Vincent Tsuei
 * @author Nathan Poslusny
 * @version 2012-11-29
 */
public class UserIO {

    private boolean interrupted = false;
    private boolean clientsTurn = false;
    
    private Client client;
    private BufferedReader reader;
    private BufferedWriter writer;

    public UserIO(Client c) {
        this(c, new BufferedReader(new InputStreamReader(System.in)), new BufferedWriter(new OutputStreamWriter(System.out)));
    }

    public UserIO(Client c, BufferedReader br, BufferedWriter bw) {
        this.client = c;
        this.reader = br;
        this.writer = bw;

    }

    /**
     * Prompt the user for a yes or no (or quit).
     * msg defines a message to send the user.
     * def defines the default option (i.e. what is chosen when the user simply presses Enter).
     * Selecting quit is only allowed if allowQuit is set to true.
     */
    public Boolean promptYesNo(String msg, String def, boolean allowQuit) throws InterruptedException {
        String ret = promptString(msg, def);
        Boolean convert = null;
        while (!ret.equalsIgnoreCase("Y") && !ret.equalsIgnoreCase("N") && (!allowQuit || !ret.equalsIgnoreCase("Q"))) {
            write("Invalid Input! Please try again ...", true);
            ret = promptString(msg, def);
        }

        if (ret.equalsIgnoreCase("Y")) {
            convert = true;
        } else if (ret.equalsIgnoreCase("N")) {
            convert = false;
        } else if (allowQuit && ret.equalsIgnoreCase("Q")) {
            convert = null;
        }
        return convert;
    }

    /**
     * Prompt the user for a String.
     * msg defines a message to send the user.
     * def defines the default option (i.e. what is chosen when the user simply presses Enter).
     */
    public String promptString(String msg, String def) throws InterruptedException {
        try {
            String ret = def;
            if (def == null) {
                write(msg + ": ", false);
            } else {
                write(msg + " [Default - " + def + "]: ", false);
            }
            
            while (!reader.ready()) {
                Thread.sleep(200); //Forced to do this since System.in is non-interruptible.
            }
            
            Thread.interrupted(); //Clear the interruption. We cannot allow the flag to propagate up.
            String userInput = reader.readLine();
            
            if (userInput != null && !userInput.isEmpty()) {
                ret = userInput;
            }
            return ret;
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Prompt the user for an Integer.
     * msg defines a message to send the user.
     * def defines the default option (i.e. what is chosen when the user simply presses Enter).
     */
    public int promptInteger(String s, String def) throws InterruptedException {
        String ret = promptString(s, def);
        Integer convert = null;
        while (convert == null) {
            try {
                convert = Integer.parseInt(ret);
            } catch (NumberFormatException nfe) {
                write("Invalid Input! Please try again ...", true);
                ret = promptString(s, def);
            }
        }
        return convert;
    }

    /**
     * Prompts for a menu option. Also shows the menu is specified.
     */
    private String PrintMenuAndPrompt(boolean printMenu) throws InterruptedException {
        if (printMenu) {
            PrintMenu();
        }
        return promptString("Please pick an option", null);
    }    
    
    
    /**
     * Print the main menu.
     */
    public void PrintMenu() {
        String menu = ClientCommand.getMenuString();
        write(menu, true);
        write("", true);
        if (client.isPlaying()) {
            PrintGameStats();
        }
    }
    
    /**
     * Print the game stats. Does nothing if not in game.
     */
    public void PrintGameStats() {
        if (client.isPlaying()) {
            write("Current game information:", true);
            write(client.getGame().getGameStateString(), true);
        }
    }
    
    /**
     * Allows other classes to signal the UserIO object to get
     * a command. Shows the menu first if specified. Incorrect
     * inputs will NOT show the menu again.
     * 
     * This function partially validates the function:
     * 1. Checks that the command exists.
     * 2. Checks that the command has the proper amount of arguments.
     */
    public Pair<ClientCommand, String[]> PromptMenu(boolean showMenu) throws InterruptedException {
        String prompt = PrintMenuAndPrompt(showMenu);
        
        while (true) {
            if (prompt == null || prompt.trim().isEmpty()) {
                write("Invalid Input! Please try again ... (Type 'help' to show the help menu)", true);
                prompt = PrintMenuAndPrompt(false);
            } else {
                String[] parse = prompt.trim().split(" +");
                String[] args = new String[parse.length - 1];
                for (int i = 1;i < parse.length;i++) {
                    args[i - 1] = parse[i];
                }
                ClientCommand command = ClientCommand.getCommandByString(parse[0]);
                if (command == null) {
                    prompt = null; //Invalid command, send them back to menu.
                } else {
                    if (command.validNumberOfArgs(args.length)) {
                        return new Pair<ClientCommand, String[]>(command, args); //Looks like a proper command. Return it.
                    } else {
                        prompt = null; //Boot them back to options.
                    }
                }
            }
        }
    }
    
    /**
     * Prints the list of players to the user output
     * 
     * @param playerList 
     */
    public void printPlayers(ArrayList<String> playerList) throws InterruptedException {
        write("",true);
        write("=========Available Players========", true);
        for (int i=0; i < playerList.size(); i++) {
            write(playerList.get(i), true);      
        }
        PromptMenu(false);
    }
    
    /**
     * Write a string to the console. Adds a newline character if newLine is true.
     */
    public void write(String s, boolean newLine) {
        try {
            writer.write(s);
            if (newLine) {
                writer.newLine();
            }
            writer.flush();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
    
    public void setInterrupt(boolean clientsTurn) {
        this.interrupted = true;
        this.clientsTurn = clientsTurn;
    }
    
    public void unsetInterrupt() {
        this.interrupted = false;
    }
    
    public boolean getInterruptClientsTurn() {
        return this.clientsTurn;
    }
    
    /**
     * Drain the user's input.
     */
    public void drain() {
        try {
            while (reader.ready()) {
                reader.read();
            }
        } catch (IOException ex) {
            Logger.getLogger(UserIO.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
